package map;

import inputstream.FileObject;

import java.awt.Component;
import java.awt.Rectangle;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.List;
import java.util.Set;

import mapelements.background.Background;
import mapelements.foreground.Foreground;

import errors.AlreadyUsedKeyException;
import errors.CoordinatesNotAcceptableException;
import errors.DimensionNotAcceptableException;
import errors.HeightNotAcceptableException;
import errors.KeyNotFoundException;
import errors.MapResolutionNotAcceptableException;
import errors.StratumNotFoundException;
import errors.WidthNotAcceptableException;

/**rappresenta una qualunque mappa di gioco RPG. Questa mappa ha le seguenti
 * caratteristiche principali:
 * <ul>
 *  <li>la mappa possiede 2 dimensioni, non una di piu', ne' una di meno</li>
 *  <li>la mappa e' rettangolare, non di altra forma (quadrata al massimo)</li>
 *  <li>la mappa comprende un {@link #background}, rappresentante la texture usata come sfondo
 *  nella mappa</li>
 * </ul>
 * 
 * <p>le dimensioni della mappa sono la {@link #width} e la {@link #height} indicanti,
 * rispettivamente, la lunghezza e l'altezza della mappa. L'unita' di misura cui si
 * riferiscono e' la {@link #mapresolution}.
 * <p>La mappa non e' altro che un enorme tabella composta da varie celle: ogni cella e'
 * quadrata e di lato {@link #mapresolution} pixel: la relativa {@link #width} e {@link #height}
 * , moltiplicate per la risoluzione, danno le dimensioni effettive della mappa.
 * 
 * <p>Da notare che la classe non comprende uno degli elementi fondamentali, cioe' una
 * serie di Foreground, indispensabili per rendere la mappa reale. Questo perche' si e'
 * preferito introdurre tale oggetto nelle sue classi figlie, in modo che la sua implementazione
 * pratica potesse diversificarsi.
 * 
 * <p>Per poter sfruttare al meglio le proprietà della AbstractMap, le classi figlie <strong>dovrebbero
 * estendere i metodi {@link #clone()},{@link #setDrawingComponent(Component)} e {@link #setDrawbleMap(AbstractMap, Component)}</strong>.
 * 
 * @author Koldar
 * @version 1.8
 */
public abstract class AbstractMap extends Object implements Serializable,Cloneable{

	private static final long serialVersionUID = -2977198149571542410L;
	
	//FIXME forse sarebbe meglio mettere gli attributi in private... (o in default): così per accedervi puoi passare solo tramite i getter!
	
	/**indica la lunghezza, in {@link #mapresolution}, della mappa*/
	protected int width;
	/**indica l'altezza, in {@link #mapresolution}, della mappa*/
	protected int height;
	/**indica la lunghezza del lato di ogni cella (quadrata) in termini di pixel*/ 
	protected int mapresolution;
	/**indica il background da usare per questa mappa*/
	protected Background background;
	/**indica la classe che si occupa di inoltrare i messaggi ad ogni Listener in caso di modifica mappa*/
	private transient MapEventDispatcher manager;
	
	/**costruisce una mappa astratta esattamente identica a quella pssata come parametro
	 * 
	 * @param other la classe da copiare
	 */
	protected AbstractMap(AbstractMap other){
		super();
		this.manager=new MapEventDispatcher();
		this.width=other.width;
		this.height=other.height;
		this.mapresolution=other.mapresolution;
		this.background=other.background;
	}
	protected AbstractMap(int _mapresolution,int _width,int _height) throws DimensionNotAcceptableException{
		this(_mapresolution,_width,_height,null);
	}
	/**rappresenta il costruttore base della classe. esso e' in grado di costruire al meglio
	 * una mappa specificando le variabili in gioco in modo semplice e veloce
	 * 
	 * @param _mapresolution indica la {@link #mapresolution} da usare
	 * @param _width indica la {@link #width} da usare
	 * @param _height indica la {@link #height} da usare
	 * @param background indica il {@link #background} da usare come sfondo della mappa
	 * @throws DimensionNotAcceptableException eccezione lanciata se tra mapresolution, width o height c'è almeno un valore negativo o nullo
	 */
	protected AbstractMap(int _mapresolution,int _width,int _height,Background background) throws DimensionNotAcceptableException{
		this.manager=new MapEventDispatcher();
		if (_width<=0){
			throw new WidthNotAcceptableException(_width);
		}
		this.width=_width;
		if (_height<=0){
			throw new HeightNotAcceptableException(_height);
		}
		this.height=_height;
		if (_mapresolution<=0){
			throw new WidthNotAcceptableException(_mapresolution);
		}
		this.mapresolution=_mapresolution;
		this.background=background;
	}
	
	/**aggiunge un nuovo strato in cima alla lista degli strati presenti nella mappa. Esegue il metodo {@link #addStratum()}
	 * 
	 */
	public void addCeilingStratum(){
		this.addStratum();
	}
	/**aggiunge il nuovo strato in fondo alla lista degli strati presenti.
	 * 
	 */
	public abstract void addFloorStratum();
	/**aggiunge il foreground specificato nello strato specificato.
	 * Se tale strato non e' ancora presente, viene lanciata un'eccezione.
	 * <p>Inoltre, in caso il metodo abbia aggiunto con successo il foreground, il metodo deve lanciare
	 * il metodo {@link #fireInsertForegroundEvent(Foreground)}
	 * <p>In caso in cui la chiave sia già stata usata per un altro foreground, il metodo genera un eccezione.
	 * <p>Il metodo, se riesce ad aggiungere con successo il foreground, lancia automaticamente il metodo <code>start()</code> dello stesso:
	 * in questo modo viene avviato il foreground aggiunto in modo completamente automatico 
	 * 
	 * @param stratum lo strato in cui va inserito il foreground
	 * @param key indica la chiave per reperire velocemente il Foreground
	 * @param foreground il foreground da aggiungere
	 * @throws StratumNotFoundException in caso in cui lo strato specificato non esista nella mappa
	 * @throws AlreadyUsedKeyException in caso in cui la chiave specificata sia giò stata usata all'interno della mappa
	 * @throws CoordinatesNotAcceptableException in caso il foreground esce dalle massime coordinate supportate dalla mappa
	 */
	public abstract void addForeground(int stratum,Object key,Foreground foreground) throws StratumNotFoundException,AlreadyUsedKeyException,CoordinatesNotAcceptableException;
	/**aggiunge un listener a questa mappa
	 * 
	 * @param arg0
	 */
	public void addMapListener(MapListener arg0){
		this.manager.addListener(arg0);
	}
	
	/**aggiunge un nuovo strato in cima alla lista degli strati presenti nella mappa
	 * 
	 */
	public abstract void addStratum();
	/**crea un nuovo strato al di sopra di quello passato come parametro.
	 * Se il nuovo strato va aggiunto tra 2 vecchi (di cui uno è bottomstratum) allora
	 * sarà necessario spostare i 2 strati per far posto a questo.
	 * <p>Se bottomstratum non è presente nella mappa allora deve essere lanciata un'eccezione 
	 * 
	 * @param bottomstratum lo strato su cui deve giacere il nuovo strato
	 */
	public abstract void addStratum(int bottomstratum) throws StratumNotFoundException;
	/**aggiunge il foreground specificato nello strato specificato con la determinata chiave.
	 * Se lo strato passato non esiste nella mappa viene lanciata un eccezione. Se invece la chiave passata
	 * è già usata nella mappa il foreground nuovo sovrascriverà quello vecchio <strong>senza alcun avvertimento per l'utente</strong>.
	 * <p>Il metodo, se riesce ad aggiungere con successo il foreground, lancia automaticamente il metodo <code>start()</code> dello stesso:
	 * in questo modo viene avviato il foreground aggiunto in modo completamente automatico 
	 * 
	 * @param stratum lo strato in cui inserire il nuovo foreground
	 * @param key la chiave da usare
	 * @param foreground i lforeground da inserire
	 * @throws StratumNotFoundException in caso lo strato <tt>stratum</tt> non esista all'interno della mappa
	 * @throws CoordinatesNotAcceptableException in caso il foreground ecceda dalle dimensioni massime della mappa
	 */
	public abstract void addUnsecureForeground(int stratum, Object key,Foreground foreground) throws StratumNotFoundException,CoordinatesNotAcceptableException;
	@Override
	public AbstractMap clone() throws CloneNotSupportedException{
		AbstractMap cloned=(AbstractMap) super.clone();
		cloned.width=this.width;
		cloned.height=this.height;
		cloned.mapresolution=this.mapresolution;
		try{
			cloned.background=this.background.clone();
		}catch (NullPointerException backgroundnull){}
		return cloned;
	}
	/**
	 * @return the background
	 */
	public Background getBackground() {
		return background;
	}
	/**dato un foreground (identificato dalla sua chiave) ritorna il foreground che e' calpestato dal foreground passato come
	 * parametro. In caso il foreground passato "calpesti" contemporaneamente piu' di un foreground,
	 * il metodo puo comportarsi come ritiene piu' opportuno:
	 * <ol>
	 *  <li>puo' ritornare il primo foreground che vede</li>
	 *  <li>può ritorna il foreground più "calpestato" dal parametro</li>
	 *  <li>puo' ritornare NULL</li>
	 * </ol>
	 * ciò che è importante è mettersi d'accordo su cosa potrebbe accadere. La scelta consigliata è
	 * senza dubbio la seconda, ma è anche la più complessa da gestire. Una soluzione naive ma efficace
	 * potrebbe essere la prima.
	 * 
	 * @param key
	 * @return il foreground "calpestato" dal foreground passato come parametro o NULL se tale foreground poggia direttamente sul background
	 */
	public abstract Foreground getFloorForeground(Object key) throws KeyNotFoundException;
	
	
	/**dato un foreground ritorna una lista di foreground che stanno al di sotto del foreground stesso;
	 * il foreground passato come parametro dunque "oscura", almeno in parte, i foreground sottostanti che possono,
	 * tra l'altro, oscurarsi a vicenda.
	 * <p>Tale metodo non guarda il background, che per definizione e' sotto a qualunque foreground
	 * 
	 * @param foreground l'elemento che sta in cima ad uno insieme di foregrounds
	 * @return la lista di foreground sottostanti al foreground specificato dal parametro, o una lista vuota se tale foreground poggia direttamente sul background
	 */
	public abstract List<Foreground> getFloorStackForeground(Object key) throws KeyNotFoundException;
	/**data una chiave (che può essere un numero, una stringa o un oggetto)
	 * la mappa ritorna il suo Foreground associato. Affinche' il metodo funzioni è necessario che ogni Foreground
	 * sia identificato da un'unica <tt>key</tt>; se ciò non avviene allora il metodo ritorna la prima occorenza di
	 * un foreground che abbia come chiave key. Anche se permesso <strong>questo tipo di mappa è fortemente sconsigliato</strong>
	 * in quanto può causare effetti non previsti.
	 * 
	 * @param key la chiave che identifica univocamente il foreground
	 * @throws KeyNotFoundException in caso la chiave non esista nell'archivio
	 * @return il foreground identificato dalla chiave o un eccezione se tale foreground non e' interno alla mappa
	 */
	public abstract Foreground getForeground(Object key) throws KeyNotFoundException;
	/**dato un certo foreground (identificato da una certa chiave) ritorna lo strato in cui
	 * il foreground è posizionato
	 * 
	 * @param key la chiave che identifica il foreground
	 * @return lo strato in cui quel foreground è posizionato 
	 */
	public abstract int getForegroundStratum(Object key) throws KeyNotFoundException;
	
	/**
	 * @return the height
	 */
	public int getHeight() {
		return height;
	}
	
	/**dato un foreground contenuto nella mappa, ritorna la chiave che lo identifica
	 * 
	 * @param frg il foreground di cui si necessita la chiave
	 * @return la chiave che identifica univocamente il foreground
	 * @throws KeyNotFoundException in caso il foreground non sia contenuto nell'archivio
	 */
	public abstract Object getKey(Foreground frg) throws KeyNotFoundException;
	
	/**
	 * 
	 * @return tutte le chiavi che questa mappa contiene, o un insieme vuoto se non sono presenti
	 */
	public abstract Set getKeys();
	
	/**
	 * 
	 * @return una lista dei listener che stanno attualmente ascoltando questa mappa
	 */
	public MapListener[] getListeners(){
		return this.manager.getListeners();
	}
	/**
	 * @return the mapresolution
	 */
	public int getMapresolution() {
		return mapresolution;
	}
	/**
	 * 
	 * @return il numero di strati che questa mappa possiede
	 */
	public abstract int getStratiSize();
	/**
	 * 
	 * @param index l'indice dello strato da reperire
	 * @return lo strato che contiene vari foreground
	 * @throws StratumNotFoundException in caso l'indirizzo non punti ad alcuno strato
	 */
	public abstract Object getStratum(int index) throws StratumNotFoundException;
	
	/**
	 * @return the width
	 */
	public int getWidth() {
		return width;
	}
	
	/**rimuove il foreground dalla mappa puntato dalla chiave. In caso la chiave non
	 * punti ad alcun foreground il metodo deve lanciare un'eccezione.
	 * <p>In caso il metodo rimuova efficacemente il foreground il metodo deve anche lanciare {@link #fireDeleteForegroundEvent()} 
	 * 
	 * @throws KeyNotFoundException in caso in cui la chiave non sia presente nell'archivio
	 * @param key la chiave che identifica univocamente il Foreground
	 */
	public abstract void removeForeground(Object key) throws KeyNotFoundException;
	/**rimuove un listener di questa mappa
	 * 
	 * @param arg0
	 */
	public void removeMapListener(MapListener arg0){
		this.manager.removeListener(arg0);
	}
	
	/**elimina uno strato presente nella mappa e scala gli eventuali successivi al di sopra
	 * di quello appena eliminato. In caso l'idstratum non rappresenti alcun strato, deve
	 * essere lanciata un eccezione
	 * 
	 * @param idstratum lo strato da eliminare
	 */
	public abstract void removeStratum(int idstratum) throws StratumNotFoundException;
	
	/**imposta il valore del background. Il metodo evoca il metodo <code>terminate()</code> del Renderer del Background, quindi aggiorna
	 * ilvalore del background con quello contenuto nel parametro. Infine evoca il metodo <code>start()</code> del nuovo renderer: in questo
	 * modo, se il Renderer non fosse già stato avviato, il metodo avvia automaticamente eventuali thread del Renderer se ci sono ascoltatori della mappa, aggiorna gli ascoltatori:
	 * <ul>
	 *  <li>se il background passa da NULL ad un valore diverso da null allora viene notificato un evento INSERT</li>
	 *  <li>se il background passa da un valore diverso da null a NULL allora viene notificato un evento DELETE</li>
	 *  <li>se il background passa da un valore diverso da null ad un altro valore diverso da null allora viene notificato un evento di tipo UPGRADE</li>
	 * </ul>
	 * 
	 * @param background the background to set
	 */
	public void setBackground(Background newbackground) {
		try{//ferma l'eventuale animazione del vecchio background
			this.background.getRenderer().terminate();
		}catch (NullPointerException error){}
		
		Background oldbackground=this.background;//vecchio background
		this.background = newbackground;//nuovo background
		
		if (newbackground==null){
			if (oldbackground!=null){
				this.fireDeleteBackgroundEvent(oldbackground);
			}
		}else{
			try{//avvia (se non è già stata avviata) l'eventuale animazione del background
				this.background.getRenderer().start();
			}catch (NullPointerException error){}
			if (oldbackground!=null){
				this.fireUpgradeBackgroundEvent();
			}else{
				this.fireInsertBackgroundEvent();
			}
		}
		
	}
	/**imposta il componente che si occupa di disegnare questa mappa. Sarà compito di tale
	 * component avere un metodo paint() sensibile alla mappa (e quindi ai vari Renderers).
	 * Ogni classe che estende AbstractMap dovrebbe implementare questo metodo per fornire le indicazioni
	 * per settare i renderers dei Foregrounds.
	 * 
	 * @param parent il component su cui la mappa verrà disegnata
	 */
	public void setDrawingComponent(Component parent){
		try{
			this.background.getRenderer().setDrawingComponent(parent);
		}catch (NullPointerException error){
			
		}
	}
	
	/**esegue il metodo {@link #upgradeForeground(Object, Foreground)} senza alcuna modifica
	 * 
	 * @param key la chiave che identifica il foreground da cambiare
	 * @param newforeground il nuovo foreground che sostituirà il vecchio foreground
	 * @throws KeyNotFoundException in caso la chiave passata non sia presente nell'archivio
	 * @throws CoordinatesNotAcceptableException in caso il nuovo foreground ecceda dalle dimensioni massime della mappa
	 */
	public void setForeground(Object key,Foreground newforeground) throws KeyNotFoundException,CoordinatesNotAcceptableException{
		this.upgradeForeground(key, newforeground);
	}
	
	/**imposta la nuova altezza della mappa (ma solo in caso in cui il valore passato sia >0). In caso ci siano
	 * degli ascoltatori da notificare lancia un MapHeightEvent a tali ascoltatori (ma solo in caso il nuovo valore sia
	 * effettivamente diverso dal precedente).
	 * Se dei Foreground si trovano ad essere posizionati al di fuori della mappa tali foreground
	 * vengono immediatamente eliminati richiamando il metodo {@link #removeForeground(Object)}
	 * @param height the height to set
	 * @throws HeightNotAcceptableException in caso in cui la height introdotta sia minore
	 * di zero: in tal caso la height corrente <strong>non</strong> viene modificata!
	 */
	public void setHeight(int height) throws HeightNotAcceptableException {
		if (height<=0){
			throw new HeightNotAcceptableException(height);
		}
		if (height!=this.height){
			int temp=this.height;
			this.height = height;
			this.deleteExceedingForegrounds();
			this.fireHeightChangedEvent();
		}
	}
	
	/**imposta questa mappa in modo che sia uguale a quella passata come parametro. In caso degli ascoltatori
	 * stiano ascoltando la mappa, essi vengono avvertiti spedendo eventi di tipo MapEvent. questo metodo imposta
	 * i vari concept ma ciò che non setta è il riferimento al componente che deve disegnare la mappa. Quello è possibile
	 * settarlo tramite {@link #setDrawingComponent(Component)}
	 * 
	 * @param othermap la mappa che si vuole copiare
	 */
	public void setMap(AbstractMap othermap){
		this.setMapresolution(othermap.mapresolution);
		this.setWidth(othermap.width);
		this.setHeight(othermap.height);
		this.setBackground(othermap.background);
	}
	/**imposta la nuova risoluzione (se e solo è >0). In caso ci siano degli ascoltatori in ascolto, invia messaggi
	 * circa il cambio della risoluzione. Tale scambio di messaggi avviene se e solo la vecchia risoluzione è effettivamente
	 * diversa da quella nuova
	 * 
	 * @param _mapresolution the mapresolution to set
	 * @throws MapResolutionNotAcceptableException in caso in cui il valore introdotto
	 * sia minore o uguale di zero. in tale caso la {@link #mapresolution} <strong>non
	 * </strong> viene modificata!
	 */
	public void setMapresolution(int _mapresolution) throws MapResolutionNotAcceptableException {
		if (_mapresolution<=0){
			throw new MapResolutionNotAcceptableException(_mapresolution);
		}
		if (_mapresolution!=this.mapresolution){
			int temp=this.mapresolution;
			this.mapresolution = _mapresolution;
			this.fireMapResolutionChangedEvent();
		}
	}
	
	/**in caso il parametro passato sia >0 imposta la lunghezza della mappa. in caso siano
	 * in ascolto alcuni asoltatori, essi vengono notificati tramite un MapWidthEvent.
	 * Se dei Foreground si trovano ad essere posizionati al di fuori della mappa tali foreground
	 * vengono immediatamente eliminati richiamando il metodo {@link #removeForeground(Object)}
	 * @param width the width to set
	 * @throws WidthNotAcceptableException in caso in cui la width specificata sia minore di zero.
	 * in caso di errore la width <strong>non</strong> viene modificata!
	 */
	public void setWidth(int width) throws WidthNotAcceptableException {
		if (width<=0){
			throw new WidthNotAcceptableException(width);
		}
		if (this.width!=width){
			int temp=this.width;
			this.width = width;
			this.deleteExceedingForegrounds();
			this.fireWidthChangedEvent();
		}
	}
	
	/**il metodo termina la mappa. Terminare la mappa <strong>non significa</strong> disporla tramite
	 * Garbage Collector bensì effettuare un'operazione intermedia: congelarla. In caso di
	 * mappe con renderer animati, infatti, a volte è necessario congelare la mappa fermando ogni possibile
	 * thread all'interno (per esempio quando si vuole eliminarla per prima cosa bisogna fermare ogni thread in
	 * modo che non permangano più tardi). utilizzando questo metodo è possibile richiamare ogni metodo <tt>termiante</tt>
	 * di ogni MapElement in modo da congelare la mappa, prima di effettuare un'operazione di chiusura 
	 * 
	 */
	public void terminate(){
		try{
			this.background.getRenderer().terminate();
		} catch (NullPointerException error){}
		for (Object key:this.getKeys()){
			try{
				this.getForeground(key).getRenderer().terminate();
			}catch (NullPointerException error){}
		}
	}
	
	public void start(){
		try{
			this.background.getRenderer().start();
		} catch (NullPointerException error){}
		for (Object key:this.getKeys()){
			try{
				this.getForeground(key).getRenderer().start();
			}catch (NullPointerException error){}
		}
	}
	
	/**aggiorna il foreground puntato dalla chiave key con il nuovo foreground. In caso la chiave non esista
	 * nell'archivio il metodo deve lanciare un'eccezione. In caso invece l'upgrade avvenga correttamente, il metodo
	 * deve lanciare alla fine il metodo {@link #fireUpgradeForegroundEvent(Foreground)}.
	 * 
	 * @param key la chiave con cui sostituire il vecchio foreground con uno nuovo
	 * @throws KeyNotFoundException in caso la chiave non sia presente nell'archivio
	 * @throws CoordinatesNotAcceptableException in caso il nuovo foreground ecceda dalle dimensioni massime della mappa
	 */
	public abstract void upgradeForeground(Object key,Foreground newforeground) throws KeyNotFoundException,CoordinatesNotAcceptableException;
	
	/**lancia a tutti i listener di tipo MapListener in ascolto il messaggio: "è stato eliminato il background!".
	 * <strong>è sempre consigliato usare questo metodo dopo aver effettuato materialmente la modifica</strong>
	 * 
	 * @param oldbackground indica il vecchio background che è stato eliminato
	 */
	protected void fireDeleteBackgroundEvent(Background oldbackground){
		this.manager.fireBackgroundChanged(this, null, MapEvent.DELETE);
	}
	
	/**lancia a tutti i listener di tipo MapListener in ascolto il messaggio: "è stato eliminato un vecchio foreground!".
	 * <strong>è sempre consigliato usare questo metodo dopo aver effettuato materialmente la modifica</strong>
	 * 
	 * @param oldforeground indica il foreground che è stato eliminato
	 */
	protected void fireDeleteForegroundEvent(Foreground oldforeground){
		this.manager.fireForegroundChanged(this,oldforeground, MapEvent.DELETE);
	}
	
	/**<strong>è sempre consigliato usare questo metodo dopo aver effettuato materialmente la modifica</strong>
	 * 
	 */
	protected void fireHeightChangedEvent(){
		this.manager.fireHeightEvent(this,height);
	}
	/**lancia a tutti i listener di tipo MapListener in ascolto il messaggio: "è stato inserito il background!".
	 * <strong>è sempre consigliato usare questo metodo dopo aver effettuato materialmente la modifica</strong>
	 * 
	 * @param arg0
	 */
	protected void fireInsertBackgroundEvent(){
		this.manager.fireBackgroundChanged(this, background, MapEvent.INSERT);
	}
	
	/**lancia a tutti i listener di tipo MapListener in ascolto il messaggio: "è stato inserito un nuovo foreground!".
	 * <strong>è sempre consigliato usare questo metodo dopo aver effettuato materialmente la modifica</strong>
	 * 
	 * @param arg0
	 */
	protected void fireInsertForegroundEvent(Foreground newforeground){
		this.manager.fireForegroundChanged(this, newforeground, MapEvent.INSERT);
	}
	
	/**lancia a tutti gli ascoltatori il messaggio che la risoluzione della mappa è stata cambiata.
	 * <strong>è sempre consigliato usare questo metodo dopo aver effettuato materialmente la modifica</strong>
	 * 
	 * @param newmapresolution la nuova risoluzione
	 */
	protected void fireMapResolutionChangedEvent(){
		this.manager.fireMapResolutionEvent(this,this.mapresolution);
	}
	
	/**lancia a tutti i listener in ascolto il messaggio: "il background è stato aggiornato!".
	 * Questo si traduce con il dire che il background è passato da un valore non nullo ad un altro
	 * valore non nullo diverso dal primo.<strong>è sempre consigliato usare questo metodo dopo aver effettuato materialmente la modifica</strong>
	 * 
	 * @param oldbkg il vecchio background
	 * @param newbkg il nuovo background
	 */
	protected void fireUpgradeBackgroundEvent(){
		this.manager.fireBackgroundChanged(this,this.background,MapEvent.UPGRADE);
	}
	
	/**lancia il messaggio a tutti listener in ascolto:"un foreground è stato aggiornato!".
	 * <strong>è sempre consigliato usare questo metodo dopo aver effettuato materialmente la modifica</strong>
	 * 
	 * @param oldfrg indica il vecchio foreground
	 * @param newfrg indica il nuovo foreground
	 */
	protected void fireUpgradeForegroundEvent(Foreground newfrg){
		this.manager.fireForegroundChanged(this, newfrg, MapEvent.UPGRADE);
	}
	
	/**<strong>è sempre consigliato usare questo metodo dopo aver effettuato materialmente la modifica</strong>
	 * 
	 */
	protected void fireWidthChangedEvent(){
		this.manager.fireWidthEvent(this,this.width);
	}
	/**elimina tutti i foreground che fuoriescono dalla mappa. Questo metodo viene richiamato quando si modifica {@link #height} o {@link #width}
	 * 
	 */
	private void deleteExceedingForegrounds(){
		for (Object key:this.getKeys()){
			if (!new Rectangle(0,0,this.width,this.height).contains(this.getForeground(key).getAbsoluteShape().getBounds())){
				this.removeForeground(key);
			}
		}
	}
	public static AbstractMap loadFromFile(File file,Component drawingComponent){
		AbstractMap map=loadFromFile(file);
		if (map!=null){
			map.setDrawingComponent(drawingComponent);
		}
		return map;
	}
	public static AbstractMap loadFromURL(URL url, Component drawingComponent){
		AbstractMap map=loadFromFile(new File(url.getFile()));
		if (map!=null){
			map.setDrawingComponent(drawingComponent);
		}
		return map;
	}
	
	/**carica da un file esterno una abstractMap
	 * 
	 * @param file il file da cui reperire la mappa
	 * @return la mappa (castata in AbstractMap) oppure NULL se l'operazione non è andata a buon fine
	 */
	private static AbstractMap loadFromFile(File file){
		if (!file.exists()){//file non esistente
			return null;
		}
		try {
			ObjectInputStream stream=new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
			return (AbstractMap)stream.readObject();
		} catch (IOException | ClassCastException | ClassNotFoundException error) {
			return null;
		}
	}
	
	//TODO mettere il toString della mappa!
	
	
	
	
}
